package com.lb.bs.client.manager;

import com.alibaba.fastjson.JSON;
import com.lb.bs.client.annotation.LBClass;
import com.lb.bs.client.annotation.LBItem;
import com.lb.bs.client.annotation.LBUpdateClass;
import com.lb.bs.client.annotation.ProcessorMark;
import com.lb.bs.client.annotation.SingletonMark;
import com.lb.bs.client.beans.GroupInfo;
import com.lb.bs.client.callback.UpdateCallBackService;
import com.lb.bs.client.config.StaticConfig;
import com.lb.bs.client.factory.SingletonFactory;
import com.lb.bs.client.processor.ProcessorService;
import com.lb.bs.client.processor.enums.ProcessorType;
import com.lb.bs.client.repository.BeanHolder;
import com.lb.bs.client.repository.ScanStaticModel;
import com.lb.bs.client.util.CollectionUtils;
import com.lb.bs.client.util.KeyGenerateUtil;
import com.lb.bs.client.util.Pair;
import com.lb.bs.client.util.ReflectionUtil;
import com.lb.bs.client.util.StringUtils;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * program: LB-BS-Client
 * author: bsworld.xie
 * create: 2019-11-27 10:47
 * description:
 * reflections 有个大坑，这是个低版本，对于使用lamada,foreach的类扫描不到
 */
@SingletonMark
public class ScanMgr {
    private static final Logger logger = LoggerFactory.getLogger(ScanMgr.class);


    private ScanStaticModel scanStaticModel;

    public ScanStaticModel scanPackage(List<String> packageNames) {
        this.scanStaticModel = SingletonFactory.getInstance(ScanStaticModel.class);
        Reflections reflection = ReflectionUtil.getReflection(packageNames);
        doClazz(reflection);
        doUpdateClazz(reflection);
        return scanStaticModel;
    }


    private void doClazz(Reflections reflection) {
        Set<Class<?>> clazzList = reflection.getTypesAnnotatedWith(LBClass.class);
        scanStaticModel.setClazzList(CollectionUtils.isNotEmpty(clazzList) ? clazzList : Collections.emptySet());
        dealClazzMethod(clazzList);
        dealClazzGroupInfo(clazzList);
        dealLogPath(clazzList);

    }

    private void doUpdateClazz(Reflections reflection) {
        Set<Class<?>> updateClazzAnnos = reflection.getTypesAnnotatedWith(LBUpdateClass.class);
        Set<Class<? extends UpdateCallBackService>> finalUpdateClazzes = new HashSet<>();
        if (CollectionUtils.isNotEmpty(updateClazzAnnos)) {
            for (Class<?> clazz : updateClazzAnnos) {
                Class<?>[] interfaces = clazz.getInterfaces();
                if (CollectionUtils.isEmpty(interfaces) || !Arrays.asList(interfaces).contains(UpdateCallBackService.class)) {
                    continue;
                }
                finalUpdateClazzes.add((Class<? extends UpdateCallBackService>) clazz);
            }
        }
        scanStaticModel.setUpdateClazzList(finalUpdateClazzes);
    }

    /**
     * reflections 有个大坑，这是个低版本，对于使用lamada,foreach的类扫描不到
     */
    public Set<Class<? extends ProcessorService>> scanProcessor() {
        Reflections reflection = ReflectionUtil.getReflectionNew("com.lb.bs.client.processor");
        final Set<Class<?>> annoClazzs = reflection.getTypesAnnotatedWith(ProcessorMark.class);
        Set<Class<? extends ProcessorService>> subClazzs = reflection.getSubTypesOf(ProcessorService.class);
        if (CollectionUtils.isNotEmpty(annoClazzs) && CollectionUtils.isNotEmpty(subClazzs)) {
            subClazzs = subClazzs.stream().filter(s -> annoClazzs.stream().anyMatch(a -> s.equals(a))).filter(a -> {
                ProcessorMark[] processorMarks = a.getAnnotationsByType(ProcessorMark.class);
                ProcessorMark processorMark = processorMarks[0];
                ProcessorType type = processorMark.type();
                return type != null;
            }).collect(Collectors.toSet());
        }
        return subClazzs;
    }

    private void checkGroup(String groupName, Class clazz) {
        if (StringUtils.isEmpty(groupName)) {
            throw new IllegalStateException("lb-bs-client,groupName is empty,clazz:" + clazz);
        }
        if (!groupName.startsWith("/")) {
            throw new IllegalStateException("lb-bs-client,groupName must start with /,groupName:" + groupName + ", clazz:" + clazz);
        }
        if ("/".equals(groupName.trim())) {
            throw new IllegalStateException("lb-bs-client,groupName can not only character: \"/\",groupName:" + groupName + ", clazz:" + clazz);
        }
    }


    private void dealClazzMethod(Set<Class<?>> clazzList) {
        clazzList.forEach(clazz -> {
            List<Method> methods = Stream.of(clazz.getDeclaredMethods())
                    .filter(method -> {
                        LBItem item = method.getAnnotation(LBItem.class);
                        return item != null;
                    }).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(methods)) {
                scanStaticModel.getClazzMethodMap().put(clazz, methods);
            }
        });
    }

    private void dealClazzGroupInfo(Set<Class<?>> clazzList) {

        Map<String, GroupInfo> groupInfoMap = scanStaticModel.getGroupInfoMap();
        logger.info("dealClazzGroupInfo(), clazzList:{}, groupInfoMap:{}", JSON.toJSONString(clazzList), JSON.toJSONString(groupInfoMap));
        clazzList.forEach(clazz -> {
            LBClass[] lbAnnos = clazz.getAnnotationsByType(LBClass.class);
            LBClass clazzAnno = lbAnnos[0];
            String groupName = clazzAnno.groupName();

            checkGroup(groupName, clazz);
            GroupInfo groupInfo = groupInfoMap.get(groupName);
            if (groupInfo != null) {
                throw new IllegalStateException("lb-bs-client,重复配置分组名称,groupName:" + groupName + ", clzz:" + clazz);
            }
            groupInfo = new GroupInfo();
            groupInfo.setClazz(clazz);
            groupInfo.setEnableGray(clazzAnno.enableGray());
            groupInfo.setGroupName(groupName);
            groupInfoMap.put(groupName, groupInfo);
        });
    }


    private void dealLogPath(Set<Class<?>> clazzList) {
        ScanStaticModel scanStaticModel = this.scanStaticModel;
        StaticConfig staticConfig = BeanHolder.getHolder().getBean(StaticConfig.class);
        Map<Class<?>, List<Method>> clazzMethodMap = scanStaticModel.getClazzMethodMap();
        Map<String, String> groupLogPathMap = scanStaticModel.getGroupLogPathMap();
        List<Pair<String, String>> logPathPairs = new ArrayList<>();
        clazzList.stream()
                .map(clazz -> clazzMethodMap.get(clazz))
                .forEach(methods -> {
                    for (Method method : methods) {
                        LBItem item = method.getAnnotation(LBItem.class);
                        boolean isLogPath = item.isLogPath();
                        if (isLogPath) {
                            String fieldAnnotationKey = item.key();
                            LBClass lbClass = method.getDeclaringClass().getAnnotationsByType(LBClass.class)[0];
                            String groupName = lbClass.groupName();
                            String itemKey = KeyGenerateUtil.spliceItemKey(groupName, fieldAnnotationKey);
                            Pair<String, String> groupLogPathPair = Pair.makePair(groupName, itemKey);
                            logPathPairs.add(groupLogPathPair);
                        }
                    }
                });


        if (CollectionUtils.isEmpty(logPathPairs)) {
            return;
        }
        if (logPathPairs.size() == 1) {
            Pair<String, String> pair = logPathPairs.get(0);
            groupLogPathMap.put(pair.getFirst(), pair.getSecond());
            return;
        }
        String activeLogPath = staticConfig.getActiveLogPath();
        if (StringUtils.isEmpty(activeLogPath)) {
            throw new IllegalStateException("配置了多个日志节点，并且没有在+" + StaticConfig.class.getName() + "里指定,需要指定具体的日志节点");
        }
        for (Pair<String, String> pair : logPathPairs) {
            String itemKey = pair.getSecond();
            if (activeLogPath.equals(itemKey)) {
                groupLogPathMap.put(pair.getFirst(), pair.getSecond());
                return;
            }
        }
        throw new IllegalStateException("指定了多个日志节点，并且在" + StaticConfig.class.getName() + ".class中的activeLogPath未与配置中的任何一个日志节点匹配");
    }
}
